package com.weilus;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.http.HttpDocumentation;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.WebApplicationContext;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class ApplicationTests {

    @Autowired
    private WebApplicationContext webApplicationContext;
    protected MockMvc mockMvc;
    protected ObjectMapper objectMapper = new ObjectMapper();

    @Rule
    public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("src/docs/asciidoc/snippets/restdocs");

    @Before
    public void before(){
        String subDir = this.getClass().getSimpleName();
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
//                .addFilter(new TraceLogFilter())
                .apply(MockMvcRestDocumentation.documentationConfiguration(this.restDocumentation)
                        .snippets().withDefaults(HttpDocumentation.httpRequest(), HttpDocumentation.httpResponse()) //http-request.adoc http-response.adoc
                        .and()
                        .uris().withScheme("http").withHost("52.80.101.160").withPort(6507)
                        .and()
                        .operationPreprocessors()
                        .withRequestDefaults(Preprocessors.prettyPrint())
                        .withResponseDefaults(Preprocessors.prettyPrint())
                )
                .alwaysDo(MockMvcRestDocumentation.document(subDir+"/{method-name}/"))
                .build();

        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
            @Override
            public void serialize(LocalDateTime o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                jsonGenerator.writeNumber(o.toInstant(ZoneOffset.of("+8")).toEpochMilli());
            }
        });
        objectMapper.registerModule(timeModule);
    }

    protected <T> T get(String uri,Class<T> tClass) throws Exception {
        return this.get(uri,null,null,tClass);
    }

    protected <T> T get(String uri,HttpHeaders httpHeaders,Class<T> tClass) throws Exception {
        return this.get(uri,httpHeaders,null,tClass);
    }

    protected <T> T get(String uri,MultiValueMap<String, String> params,Class<T> tClass) throws Exception {
        return this.get(uri,null,params,tClass);
    }

    protected <T> T get(String uri,HttpHeaders httpHeaders,MultiValueMap<String, String> params,Class<T> tClass) throws Exception {
        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(uri);
        if(!ObjectUtils.isEmpty(httpHeaders))requestBuilder.headers(httpHeaders);
        if(!ObjectUtils.isEmpty(params))requestBuilder.params(params);
        MvcResult result = this.mockMvc.perform(requestBuilder)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andReturn();
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(Result.class,tClass);
        Result<T> resp = objectMapper.readValue(result.getResponse().getContentAsString(),javaType);
        objectMapper.writerWithDefaultPrettyPrinter().writeValue(System.err,resp);
        return resp.getData();
    }

    protected <T> T post(String uri, Class<T> tClass) throws Exception {
        return this.post(uri,null,null,null,tClass);
    }
    protected <T> T post(String uri,MultiValueMap<String, String> params, Class<T> tClass) throws Exception {
        return this.post(uri,null,params,null,tClass);
    }
    protected <T> T post(String uri,HttpHeaders httpHeaders,MultiValueMap<String, String> params, Class<T> tClass) throws Exception {
        return this.post(uri,httpHeaders,params,null,tClass);
    }
    protected <T> T post(String uri,Object body, Class<T> tClass) throws Exception {
        return this.post(uri,null,null,body,tClass);
    }
    protected <T> T post(String uri,HttpHeaders httpHeaders,Object body, Class<T> tClass) throws Exception {
        return this.post(uri,httpHeaders,null,body,tClass);
    }
    protected <T> T post(String uri, HttpHeaders httpHeaders,MultiValueMap<String, String> params,Object body, Class<T> tClass) throws Exception {
        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(uri);
        if(!ObjectUtils.isEmpty(httpHeaders))requestBuilder.headers(httpHeaders);
        if(!ObjectUtils.isEmpty(params))requestBuilder.params(params);
        if(!ObjectUtils.isEmpty(body))requestBuilder.contentType("application/json;charset=UTF-8").content(objectMapper.writeValueAsString(body));
        MvcResult result = this.mockMvc.perform(requestBuilder)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andReturn();
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(Result.class,tClass);
        Result<T> resp = objectMapper.readValue(result.getResponse().getContentAsString(),javaType);
        return resp.getData();
    }

}
